﻿namespace Resharper.Abstractions.CodeDom.Types
{
  using System;
  using System.Diagnostics;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.CSharp.Tree;
  using JetBrains.ReSharper.Psi.Tree;
  using Resharper.Abstractions.CodeDom.Namespaces;
  using Resharper.Abstractions.CodeDom.TreeNodes;
  using Resharper.Abstractions.CodeDom.TypeMembers;

  /// <summary>Defines the <see cref="TypeElement"/> class.</summary>
  public class TypeElement : TreeNode
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="TypeElement"/> class.</summary>
    /// <param name="typeElement">The type element.</param>
    internal TypeElement([NotNull] ITypeDeclaration typeElement) : base(typeElement)
    {
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets the access right.
    /// </summary>
    public CodeDom.AccessRights AccessRights
    {
      get
      {
        return AccessRightsHelper.GetAccessRights(this.InternalType.DeclaredElement as IAccessRightsOwner);
      }
    }

    /// <summary>
    /// Gets the containing namespace.
    /// </summary>
    [CanBeNull]
    public Namespace ContainingNamespace
    {
      get
      {
        var node = this.InternalType.GetContainingNode<INamespaceDeclaration>();

        return node != null ? new Namespace(node) : null;
      }
    }

    /// <summary>
    /// Gets the containing type element.
    /// </summary>
    [CanBeNull]
    public TypeElement ContainingType
    {
      get
      {
        var node = this.InternalType.GetContainingNode<ITypeDeclaration>();

        return node != null ? new TypeElement(node) : null;
      }
    }

    /// <summary>
    /// Gets the containing type member.
    /// </summary>
    [CanBeNull]
    public TypeMember ContainingTypeMember
    {
      get
      {
        var containingTypeMember = this.InternalType.GetContainingNode<ITypeMemberDeclaration>();

        return containingTypeMember != null ? TypeMemberFactory.GetTypeMember(containingTypeMember) : null;
      }
    }

    /// <summary>
    /// Gets the full name.
    /// </summary>
    [NotNull]
    public string FullName
    {
      get
      {
        var ns = this.ContainingNamespace;
        if (ns == null)
        {
          return this.Name;
        }

        return ns.FullName + "." + this.Name;
      }
    }

    /// <summary>
    /// Gets the name.
    /// </summary>
    [NotNull]
    public string Name
    {
      get
      {
        return this.InternalType.DeclaredName;
      }
    }

    /// <summary>The type element field.</summary>
    [NotNull]
    internal ITypeDeclaration InternalType
    {
      get
      {
        return (ITypeDeclaration)this.InternalTreeNode;
      }
    }

    #endregion

    #region Public Methods

    /// <summary>Adds the member.</summary>
    /// <param name="typeMember">The constant.</param>
    /// <param name="anchor">The anchor.</param>
    [CanBeNull]
    public TypeMember AddMember([NotNull] TypeMember typeMember, [NotNull] TypeMember anchor)
    {
      if (typeMember == null)
      {
        throw new ArgumentNullException("typeMember");
      }

      if (anchor == null)
      {
        throw new ArgumentNullException("anchor");
      }

      var classDeclaration = this.InternalType as IClassLikeDeclaration;
      if (classDeclaration == null)
      {
        return null;
      }

      var memberDeclaration = typeMember.InternalTypeMember as IClassMemberDeclaration;
      Debug.Assert(memberDeclaration != null, "memberDeclaration != null");

      var anchorDeclaration = anchor.InternalTypeMember as IClassMemberDeclaration;
      Debug.Assert(anchorDeclaration != null, "anchorDeclaration != null");

      var result = classDeclaration.AddClassMemberDeclarationBefore(memberDeclaration, anchorDeclaration);

      return result != null ? TypeMemberFactory.GetTypeMember(result) : null;
    }

    /// <summary>Adds the member.</summary>
    /// <param name="typeMember">The class member.</param>
    /// <returns>Returns the member.</returns>
    [CanBeNull]
    public TypeMember AddMember([NotNull] TypeMember typeMember)
    {
      if (typeMember == null)
      {
        throw new ArgumentNullException("typeMember");
      }

      var classDeclaration = this.InternalType as IClassLikeDeclaration;
      if (classDeclaration == null)
      {
        return null;
      }

      var memberDeclaration = typeMember.InternalTypeMember as IClassMemberDeclaration;
      Debug.Assert(memberDeclaration != null, "memberDeclaration != null");

      var result = classDeclaration.AddClassMemberDeclaration(memberDeclaration);

      return result != null ? TypeMemberFactory.GetTypeMember(result) : null;
    }

    #endregion
  }
}